package org.springframework.aop.framework.autoproxy;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.framework.ReflectiveMethodInvocation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext;

import java.lang.reflect.Field;
import java.util.List;

public class A19 {

    @Aspect
    static class MyAspect {
        @Before("execution(* foo(..))") // 静态通知调用，不带参数绑定，执行时不需要切点
        public void before1() {
            System.out.println("before1");
        }

        /**
         * 新知识：一种可以绑定参数的通知。
         */
        @Before("execution(* foo(..)) && args(x)") // 动态通知调用，需要参数绑定，执行时还需要切点对象
        public void before2(int x) {
            System.out.printf("before2(%d)%n", x);
        }
    }

    static class Target {
        public void foo(int x) {
            System.out.printf("target foo(%d)%n", x);
        }
    }

    @Configuration
    static class MyConfig {
        @Bean
        AnnotationAwareAspectJAutoProxyCreator proxyCreator() {
            return new AnnotationAwareAspectJAutoProxyCreator();
        }

        @Bean
        public MyAspect myAspect() {
            return new MyAspect();
        }
    }


    public static void main(String[] args) throws Throwable {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.registerBean(MyConfig.class);
        context.refresh();

        /**
         * spring AOP的整体逻辑（个人的思考）：
         * AOP中涉及到的概念挺多，包括切点、切面、前置、后置、环绕等等，但落到本质，其实都是底层JDK/Cglib在代理具体的方法时，
         * 能找到这个方法多匹配到的增强Advice，结合Target来完成一个增强逻辑和目标方法的结合。理解了这点，其实对于spring中AOP
         * 的很多API应该就有更深的理解。
         * 以ProxyFactory为例：我想，ProxyFactory作为生成代理类的工具，底层肯定调用了JDK/Cglib底层的API，忽略底层，ProxyFactory
         * 创建代理所需要的东西还是这么几样：Target、Advisor(切点+切面)。当然，再进一步细化，具体到每个被增强的方法时：需要的就是目标
         * 方法 + 这个方法所匹配到的Advice
         */
        AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
        List<Advisor> list = creator.findEligibleAdvisors(Target.class, "target");

        Target target = new Target();
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(target);
        factory.addAdvisors(list);
        Object proxy = factory.getProxy(); // 获取代理

        /**
         *  获得这个“foo”方法对应的通知(都被转化成了环绕通知)
         *  一般的通知都是不带参数的，但这里的通知是绑定参数的。通知里面需要获取方法的某个参数，这个信息包含在切点的execution(* foo(..)) && args(x)信息
         *  里。因此，我们可以看到getInterceptorsAndDynamicInterceptionAdvice获得的不再全部是环绕通知MethodInterceptor，而增加了一种新的类型：
         *  InterceptorAndDynamicMethodMatcher，里面包含了普通环绕通知 + 切点 。有了切点的信息，才能保证在调用切面方法时，能正确获得参数应用于切面
         */
        List<Object> interceptorList = factory.getInterceptorsAndDynamicInterceptionAdvice(Target.class.getMethod("foo", int.class), Target.class);
        for (Object o : interceptorList) {
            showDetail(o);
        }


        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>");
        ReflectiveMethodInvocation invocation = new ReflectiveMethodInvocation(
                proxy, target, Target.class.getMethod("foo", int.class), new Object[]{100}, Target.class, interceptorList
        ) {};

        invocation.proceed();
    }




    public static void showDetail(Object o) {
        try {
            Class<?> clazz = Class.forName("org.springframework.aop.framework.InterceptorAndDynamicMethodMatcher");
            if (clazz.isInstance(o)) {
                Field methodMatcher = clazz.getDeclaredField("methodMatcher");
                methodMatcher.setAccessible(true);
                Field methodInterceptor = clazz.getDeclaredField("interceptor");
                methodInterceptor.setAccessible(true);
                System.out.println("环绕通知和切点：" + o);
                System.out.println("\t切点为：" + methodMatcher.get(o));
                System.out.println("\t通知为：" + methodInterceptor.get(o));
            } else {
                System.out.println("普通环绕通知：" + o);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
